﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Vortex.Drawing {

	//this part is responsible for drawing with canvas
	public sealed partial class Canvas2D {


		#region Drawing Textured Rectangles

		/// <summary>
		/// Stretches the entire texture into destRect on canvas.
		/// </summary>
		/// <param name="destRect">The dest rect.</param>
		/// <param name="texture">The texture.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawTexture(Rect destRect, BaseTexture texture, ColorU colorTint) {
			DrawTexturedRect(destRect, texture, texture.Region, colorTint);
		}

		/// <summary>
		/// Stretches the source rect of texture into destRect on canvas.
		/// </summary>
		/// <param name="destRect">The dest rect.</param>
		/// <param name="texture">The texture.</param>
		/// <param name="srcRect">The source rect.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawTexturedRect(Rect destRect, BaseTexture texture, Rect srcRect, ColorU colorTint) {
			if (null == texture) throw new ArgumentNullException("texture");

			_renderer.SetCanvas(this, texture);

			//requests to add new vertex fragment 
			FragmentLocation location;
			_renderer.AllocateFragment(VertexFragment.Quad, out location);

			//populate fragment vertices
			Rect textureRegion;
			texture.Transform(ref srcRect, out textureRegion);

			unsafe {
				Vertex2D* vertices = location.Vertices;
				vertices[0].XY = destRect.LeftTop;
				vertices[0].Color = colorTint;
				vertices[0].UV1 = textureRegion.LeftTop;
				vertices[0].Z = _Z;

				vertices[1].XY = destRect.RightTop;
				vertices[1].Color = colorTint;
				vertices[1].UV1 = textureRegion.RightTop;
				vertices[1].Z = _Z;

				vertices[2].XY = destRect.RightBottom;
				vertices[2].Color = colorTint;
				vertices[2].UV1 = textureRegion.RightBottom;
				vertices[2].Z = _Z;

				vertices[3].XY = destRect.LeftBottom;
				vertices[3].Color = colorTint;
				vertices[3].UV1 = textureRegion.LeftBottom;
				vertices[3].Z = _Z;

				//apply transform
				Transform4Vertices(ref _transform, vertices);

				//populate indices
				PopulateQuadIndices(ref location);
			}
		}


		#endregion

		#region Drawing Sprites

		/// <summary>
		/// Draws the sprite into specified region.
		/// </summary>
		/// <param name="region">The region.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(Rect region, Sprite sprite, ColorU colorTint) {
			if (null == sprite) throw new ArgumentNullException("sprite");

			_renderer.SetCanvas(this, sprite.Texture);

			//requests to add new vertex fragment 
			FragmentLocation location;
			_renderer.AllocateFragment(VertexFragment.Quad, out location);

			//populate fragment vertices
			Rect textureRegion = sprite.TransformedRegion;

			unsafe {
				Vertex2D* vertices = location.Vertices;
				
				vertices[0].XY = region.LeftTop;
				vertices[0].Color = colorTint;
				vertices[0].UV1 = textureRegion.LeftTop;

				vertices[1].XY = region.RightTop;
				vertices[1].Color = colorTint;
				vertices[1].UV1 = textureRegion.RightTop;

				vertices[2].XY = region.RightBottom;
				vertices[2].Color = colorTint;
				vertices[2].UV1 = textureRegion.RightBottom;

				vertices[3].XY = region.LeftBottom;
				vertices[3].Color = colorTint;
				vertices[3].UV1 = textureRegion.LeftBottom;
				
				if (null != _currentBrush) {
					FillBrushCoords(vertices);
				}
				
				//apply transform
				Transform4Vertices(ref _transform, vertices);

				//populate indices
				PopulateQuadIndices(ref location);
			}
		}

		/// <summary>
		/// Draws the sprite at point (point matches sprite origin point)
		/// </summary>
		/// <param name="spritePoint">The sprite point.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(Vector2 spritePoint, Sprite sprite, ColorU colorTint) {
			if (null == sprite) throw new ArgumentNullException("sprite");

			Rect region = Rect.FromBox(spritePoint - sprite.Origin, sprite.Size);
			DrawSprite(region, sprite, colorTint);
		}

		/// <summary>
		/// Draws the sprite at point (point matches sprite origin point)
		/// </summary>
		/// <param name="x">The x position of sprite.</param>
		/// <param name="y">The y position of sprite.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(float x, float y, Sprite sprite, ColorU colorTint) {
			DrawSprite(new Vector2(x, y), sprite, colorTint);
		}


		/// <summary>
		/// Draws the sprite with overriden size at point (point matches sprite origin point)
		/// </summary>
		/// <param name="spritePoint">The sprite point.</param>
		/// <param name="sizeOverride">The size override (original size is discarded).</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(Vector2 spritePoint, Vector2 sizeOverride, Sprite sprite, ColorU colorTint) {
			if (null == sprite) throw new ArgumentNullException("sprite");

			Rect region = Rect.FromBox(spritePoint - sprite.Origin * (sizeOverride / sprite.Size), sizeOverride);
			DrawSprite(region, sprite, colorTint);
		}

		/// <summary>
		/// Draws the sprite with overriden size at point (point matches sprite origin point)
		/// </summary>
		/// <param name="x">The x position of sprite.</param>
		/// <param name="y">The y position of sprite.</param>
		/// <param name="widthOverride">The sprite width override.</param>
		/// <param name="heightOverride">The sprite height override.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(float x, float y, float widthOverride, float heightOverride, Sprite sprite, ColorU colorTint) {
			DrawSprite(new Vector2(x, y), new Vector2(widthOverride, heightOverride), sprite, colorTint);
		}

		/// <summary>
		/// Draws the sprite at point (point matches sprite origin point) with rotation around it
		/// </summary>
		/// <param name="point">The sprite point.</param>
		/// <param name="rotationAngleRad">The rotation of sprite around point in radians.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(Vector2 point, float rotationAngleRad, Sprite sprite, ColorU colorTint) {
			if (null == sprite) throw new ArgumentNullException("sprite");

			DrawSprite(point, sprite.Size, rotationAngleRad, sprite, colorTint);
		}

		/// <summary>
		/// Draws the sprite with overriden size at point (point matches sprite origin point) with rotation around it
		/// </summary>
		/// <param name="point">The sprite point.</param>
		/// <param name="sizeOverride">The sprite size override.</param>
		/// <param name="rotationAngleRad">The rotation of sprite around point in radians.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(Vector2 point, Vector2 sizeOverride, float rotationAngleRad, Sprite sprite, ColorU colorTint) {
			if (null == sprite) throw new ArgumentNullException("sprite");
			if (sprite.Size.IsEmpty) return;

			_renderer.SetCanvas(this, sprite.Texture);

			//requests to add new vertex fragment 
			FragmentLocation location;
			_renderer.AllocateFragment(VertexFragment.Quad, out location);

			//populate fragment vertices
			Rect region = Rect.FromBox(point - sprite.Origin * (sizeOverride / sprite.Size), sizeOverride);
			Rect textureRegion = sprite.TransformedRegion;

			unsafe {
				Vertex2D* vertices = location.Vertices;
				vertices[0].XY = region.LeftTop;
				vertices[0].Color = colorTint;
				vertices[0].UV1 = textureRegion.LeftTop;

				vertices[1].XY = region.RightTop;
				vertices[1].Color = colorTint;
				vertices[1].UV1 = textureRegion.RightTop;

				vertices[2].XY = region.RightBottom;
				vertices[2].Color = colorTint;
				vertices[2].UV1 = textureRegion.RightBottom;

				vertices[3].XY = region.LeftBottom;
				vertices[3].Color = colorTint;
				vertices[3].UV1 = textureRegion.LeftBottom;

				if (null != _currentBrush) {
					FillBrushCoords(vertices);
				}

				//apply transform				
				Transform2D spriteTransform;
				Transform2D.CreateRotation(rotationAngleRad, ref point, out spriteTransform);
				Transform4Vertices(ref spriteTransform, vertices);
				Transform4Vertices(ref _transform, vertices);

				//populate indices
				PopulateQuadIndices(ref location);
			}
		}

		/// <summary>
		/// Draws the sprite at point (point matches sprite origin point) with rotation around it
		/// </summary>
		/// <param name="x">The x position of sprite.</param>
		/// <param name="y">The y position of sprite.</param>
		/// <param name="rotationAngleRad">The rotation of sprite around point in radians.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(float x, float y, float rotationAngleRad, Sprite sprite, ColorU colorTint) {
			if (null == sprite) throw new ArgumentNullException("sprite");

			DrawSprite(new Vector2(x, y), sprite.Size, rotationAngleRad, sprite, colorTint);
		}

		/// <summary>
		/// Draws the sprite with overriden size at point (point matches sprite origin point) with rotation around it
		/// </summary>
		/// <param name="x">The x position of sprite.</param>
		/// <param name="y">The y position of sprite.</param>
		/// <param name="widthOverride">The sprite width override.</param>
		/// <param name="heightOverride">The sprite height override.</param>
		/// <param name="rotationAngleRad">The rotation of sprite around point in radians.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawSprite(float x, float y, float widthOverride, float heightOverride, float rotationAngleRad, Sprite sprite, ColorU colorTint) {
			DrawSprite(new Vector2(x, y), new Vector2(widthOverride, heightOverride), rotationAngleRad, sprite, colorTint);
		}


		#endregion

		#region Drawing Elementary

		/*
		public void DrawQuad(ref Vertex2D v1, ref Vertex2D v2, ref Vector3 v3, ref Vector4 v4, BaseTexture texture, DrawingFlags flags) {

		}
		*/

		#endregion

		#region Complex Drawings

		/// <summary>
		/// Draws the textured frame as grid 3x3. Corners are always drawn 1:1, sides are scaled only by one axis.
		/// Drawing center is optional. Center scales by X and Y.
		/// </summary>
		/// <param name="destRegion">The destination region.</param>
		/// <param name="frameSprite">The source frame sprite.</param>
		/// <param name="frameSize">Size of the frame border in pixels.</param>
		/// <param name="drawCenter">if set to <c>true</c> then draws frame central part.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawFrame(Rect destRegion, Sprite frameSprite, Vector2 frameSize, bool drawCenter, ColorU colorTint) {
			if (null == frameSprite) throw new ArgumentNullException("frameSprite");

			DrawFrame(destRegion, frameSprite.Texture, frameSprite.Region, frameSize, drawCenter, colorTint);
		}

		/// <summary>
		/// Draws the textured frame as grid 3x3. Corners are always drawn 1:1, sides are scaled only by one axis.
		/// Drawing center is optional. Center scales by X and Y. 
		/// </summary>
		/// <param name="destRegion">The destination region.</param>
		/// <param name="texture">Source texture.</param>
		/// <param name="sourceRegion">The source region.</param>
		/// <param name="frameSize">Size of the frame border in pixels.</param>
		/// <param name="drawCenter">if set to <c>true</c> then draws frame central part.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawFrame(Rect destRegion, BaseTexture texture, Rect sourceRegion, Vector2 frameSize, bool drawCenter, ColorU colorTint) {
			if (null == texture) throw new ArgumentNullException("texture");

			if (frameSize.X < 0 || frameSize.Y < 0) {
				throw new ArgumentException("Frame border size shoud be zero or greater");
			}

			Rect d_o = destRegion, d_i = d_o.Deflate(frameSize);
			Rect s_o = sourceRegion, s_i = s_o.Deflate(frameSize);
			bool hasWidth = frameSize.X != 0, hasHeight = frameSize.Y != 0;

			//draw elements
			if (hasWidth && hasHeight) {
				DrawTexturedRect(new Rect(d_o.Left, d_o.Top, d_i.Left, d_i.Top), texture, new Rect(s_o.Left, s_o.Top, s_i.Left, s_i.Top), colorTint);
			}
			if (hasHeight) {
				DrawTexturedRect(new Rect(d_i.Left, d_o.Top, d_i.Right, d_i.Top), texture, new Rect(s_i.Left, s_o.Top, s_i.Right, s_i.Top), colorTint);
			}
			if (hasWidth && hasHeight) {
				DrawTexturedRect(new Rect(d_i.Right, d_o.Top, d_o.Right, d_i.Top), texture, new Rect(s_i.Right, s_o.Top, s_o.Right, s_i.Top), colorTint);
			}
			if (hasWidth) {
				DrawTexturedRect(new Rect(d_o.Left, d_i.Top, d_i.Left, d_i.Bottom), texture, new Rect(s_o.Left, s_i.Top, s_i.Left, s_i.Bottom), colorTint);
			}

			if (hasWidth) {
				DrawTexturedRect(new Rect(d_i.Right, d_i.Top, d_o.Right, d_i.Bottom), texture, new Rect(s_i.Right, s_i.Top, s_o.Right, s_i.Bottom), colorTint);
			}
			if (hasWidth && hasHeight) {
				DrawTexturedRect(new Rect(d_o.Left, d_i.Bottom, d_i.Left, d_o.Bottom), texture, new Rect(s_o.Left, s_i.Bottom, s_i.Left, s_o.Bottom), colorTint);
			}
			if (hasHeight) {
				DrawTexturedRect(new Rect(d_i.Left, d_i.Bottom, d_i.Right, d_o.Bottom), texture, new Rect(s_i.Left, s_i.Bottom, s_i.Right, s_o.Bottom), colorTint);
			}
			if (hasWidth && hasHeight) {
				DrawTexturedRect(new Rect(d_i.Right, d_i.Bottom, d_o.Right, d_o.Bottom), texture, new Rect(s_i.Right, s_i.Bottom, s_o.Right, s_o.Bottom), colorTint);
			}

			if (drawCenter) {
				DrawTexturedRect(new Rect(d_i.Left, d_i.Top, d_i.Right, d_i.Bottom), texture, new Rect(s_i.Left, s_i.Top, s_i.Right, s_i.Bottom), colorTint);
			}
		}

		/// <summary>
		/// Draws the sprite entity.
		/// </summary>
		/// <param name="spriteEntity">The sprite entity via interface.</param>
		public void DrawSpriteEntity(ISpriteEntity spriteEntity) {
			if (null == spriteEntity) throw new ArgumentNullException("spriteEntity");

			Vector2 xy, size;
			float angle;
			ColorU colorTint;
			Sprite sprite;

			spriteEntity.QueryParameters(out xy, out size, out angle, out colorTint, out sprite);

			DrawSprite(xy, size, angle, sprite, colorTint);
		}

		#endregion

		#region Tesselated Drawing

		/// <summary>
		/// Draws tessellated sprite in specified region. .
		/// </summary>
		/// <param name="region">The target region.</param>
		/// <param name="sprite">The sprite to draw.</param>
		/// <param name="colorTint">The color tint.</param>
		/// <param name="splitX">The split by X axis, should be &gt; 1.</param>
		/// <param name="splitY">The split by Y axis, should be &gt; 1.</param>
		public void DrawTessellatedSprite(Rect region, Sprite sprite, ColorU colorTint, int splitX, int splitY) {
			if (null == sprite) throw new ArgumentNullException("sprite");

			Rect uvRegion = sprite.TransformedRegion;
			DrawTessellatedRegion(ref region, sprite.Texture, ref uvRegion, colorTint, splitX, splitY);
		}

		/// <summary>
		/// Draws tessellated textured rectangle in specified region. .
		/// </summary>
		/// <param name="region">The target region.</param>
		/// <param name="texture">The texture to use.</param>
		/// <param name="sourceRegion">The source region on texture.</param>
		/// <param name="colorTint">The color tint.</param>
		/// <param name="splitX">The split by X axis, should be &gt; 1.</param>
		/// <param name="splitY">The split by Y axis, should be &gt; 1.</param>
		public void DrawTessellatedTexturedRect(Rect region, BaseTexture texture, Rect sourceRegion, ColorU colorTint, int splitX, int splitY) {
			if (null == texture) throw new ArgumentNullException("texture");

			DrawTessellatedRegion(ref region, texture, ref sourceRegion, colorTint, splitX, splitY);
		}

		/// <summary>
		/// Draws tessellated rectangle in specified region. .
		/// </summary>
		/// <param name="region">The target region.</param>
		/// <param name="colorTint">The color tint.</param>
		/// <param name="splitX">The split by X axis, should be &gt; 1.</param>
		/// <param name="splitY">The split by Y axis, should be &gt; 1.</param>
		public void DrawTessellatedRect(Rect region, ColorU colorTint, int splitX, int splitY) {
			Rect empty = Rect.Empty;
			DrawTessellatedRegion(ref region, null, ref empty, colorTint, splitX, splitY);
		}

		/// <summary>
		/// Internal implementation of drawing tessellated region.
		/// </summary>
		/// <param name="region">The target region.</param>
		/// <param name="texture">The texture. May be null</param>
		/// <param name="uvRegion">The region of source transformed UV coordinates.</param>
		/// <param name="colorTint">The color tint.</param>
		/// <param name="splitX">The split by X axis, should be > 1.</param>
		/// <param name="splitY">The split by Y axis, should be > 1.</param>
		private void DrawTessellatedRegion(ref Rect region, BaseTexture texture, ref Rect uvRegion, ColorU colorTint, int splitX, int splitY) {
			_renderer.SetCanvas(this, texture);

			if (splitX < 2 || splitY < 2) {
				throw new ArgumentException("splitX/splitY should be greater than 1");
			}

			int cellX = splitX - 1, cellY = splitY - 1;
			int quadCount = cellX * cellY, vertexCount = splitX * splitY, indexCount = quadCount * 6;

			VertexFragment fragment = new VertexFragment(vertexCount, indexCount, PrimitiveType.TriangleList);
			FragmentLocation location;			
			_renderer.AllocateFragment(fragment, out location);

			Rect uvRegion2 = (_currentBrush != null) ? _currentBrush.TransformedRegion : Rect.Empty;

			unsafe {
				//prepare vertices data
				Vertex2D* vertices = location.Vertices;

				Vector2 cells = new Vector2(cellX, cellY);
				Vector2 xyStep = region.Size / cells;
				Vector2 xyOffset = region.LeftTop;
				Vector2 uvStep = uvRegion.Size / cells;
				Vector2 uvOffset = uvRegion.LeftTop;
				Vector2 uvStep2 = uvRegion2.Size / cells;
				Vector2 uvOffset2 = uvRegion2.LeftTop;

				//fill vertices data
				for (int n = 0; n < splitY; ++n) {
					for (int m = 0; m < splitX; ++m) {
						vertices->XY = xyOffset;
						vertices->Z = _Z;
						vertices->Color = colorTint;
						vertices->UV1 = uvOffset;
						vertices->UV2 = uvOffset2;
						//update loop pointers
						xyOffset.X += xyStep.X;
						uvOffset.X += uvStep.X;
						uvOffset2.X += uvStep2.X;
						//increment pointer
						vertices++;
					}
					//update pointers on end				
					xyOffset = new Vector2(region.Left, xyOffset.Y + xyStep.Y);
					uvOffset = new Vector2(uvRegion.Left, uvOffset.Y + uvStep.Y);
					uvOffset2 = new Vector2(uvRegion2.Left, uvOffset2.Y + uvStep2.Y);
				}

				//check is transformation required
				if (!_transform.IsEmpty) {
					TransformVertices(ref _transform, vertexCount, location.Vertices);
				}

				//populate indices as well
				ushort* indices = location.Indices;
				for (int n = 0; n < cellY; ++n) {
					int baseThis = location.BaseIndex + n * splitX, baseNext = baseThis + splitX;
					for (int m = 0; m < cellX; ++m) {
						//triangle N1
						*indices++ = (ushort)baseThis;
						*indices++ = (ushort)(baseThis + 1);
						*indices++ = (ushort)(baseNext + 1);
						//triangle N2
						*indices++ = (ushort)baseThis;
						*indices++ = (ushort)(baseNext + 1);
						*indices++ = (ushort)baseNext;
						//increment base
						baseThis++;
						baseNext++;
					}
				}
			}
		}

		#endregion
	}
}
